﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Reflection;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC.Configuration;

namespace CoreEx.DynamicInvoker.Implementation
{
    /// <summary>
    /// Base class that outlines the basic structure of a <see cref="DynamicMethod"/> capable of accessing a class member. 
    /// </summary>
    /// <typeparam name="TMember">The type of member that the <see cref="DynamicMethod"/> operates on</typeparam>
    public abstract class DynamicFactoryBase<TMember> : IDynamicFactory<TMember> where TMember : MemberInfo
    {      
        /// <summary>
        /// Creates a <see cref="MethodHandler"/> delegate that can be used to invoke the member.
        /// </summary>
        /// <param name="targetMember">The member that will be invoked.</param>
        /// <returns>A <see cref="MethodHandler"/> that can be used to invoke the member.</returns>
        public MethodHandler CreateDelegate(TMember targetMember)
        {            
            //Create a new dynamic method.
            DynamicMethod dynamicMethod = CreateDynamicMethod(targetMember,true);
            
            ILGenerator iLGenerator = dynamicMethod.GetILGenerator();

            //Load the instance
            LoadInstance(iLGenerator,targetMember);

            //Load the parameters
            LocalBuilder[] localMembers = LoadParameters(iLGenerator,targetMember);            

            //Form the method body
            EmitMethodBody(iLGenerator,targetMember);

            //Complete the method and return.
            Return(iLGenerator, targetMember,localMembers);

            //Finally create the delegate to be used for invocation.
            return (MethodHandler) dynamicMethod.CreateDelegate(typeof (MethodHandler));
        }

        /// <summary>
        /// Completes the <see cref="DynamicMethod"/> and returns the return value if any. 
        /// </summary>
        /// <param name="iLGenerator">The <see cref="iLGenerator"/> used to create the <see cref="DynamicMethod"/>.</param>
        /// <param name="targetMember">The member that the <see cref="DynamicMethod"/> operates on.</param>
        protected virtual void Return(ILGenerator iLGenerator, TMember targetMember,LocalBuilder[] localVariables)
        {
            //Get the parameters 
            var parameterTypes = GetParameters(targetMember).ToArray();

            //Check to see of any of the parameters is passed by ref
            for (int i = 0; i < parameterTypes.Length; i++)
            {
                if (parameterTypes[i].IsByRef)
                {
                    //Load the second method parameter onto the evaluation stack
                    //This is the parameter array passed to the dynamic method
                    iLGenerator.Emit(OpCodes.Ldarg_1);

                    EmitFastInt(iLGenerator, i);

                    //Load the local variable onto the evaluation stack
                    iLGenerator.Emit(OpCodes.Ldloc, localVariables[i]);

                    // Check to see of we need to box the value
                    if (localVariables[i].LocalType.IsValueType)
                        iLGenerator.Emit(OpCodes.Box, localVariables[i].LocalType);
                    
                    //Update the parameter value to reflect the changes made to the local variable 
                    iLGenerator.Emit(OpCodes.Stelem_Ref);

                }

            }                                                
            //Retrieve the return type
            Type returnType = GetReturnType(targetMember);
            if (returnType != typeof(void))
            {
                //Check to see if we need to box the value before returning from the method.
                if (returnType.IsValueType)
                    iLGenerator.Emit(OpCodes.Box, returnType);
            }
            else
                //The member has no return value.                
                if (targetMember.MemberType != MemberTypes.Constructor)
                    iLGenerator.Emit(OpCodes.Ldnull);

            //Complete the dynamic method by returning to the caller. 
            iLGenerator.Emit(OpCodes.Ret);
        }
               
        /// <summary>
        /// Loads the target instance onto the evaluation stack.
        /// </summary>
        /// <param name="iLGenerator">The <see cref="iLGenerator"/> used to create the <see cref="DynamicMethod"/>.</param>
        /// <param name="targetMember"></param>
        protected abstract void LoadInstance(ILGenerator iLGenerator, TMember targetMember);



        //protected virtual void LoadParameters(ILGenerator iLGenerator, TMember member)
        //{
        //    //Get the parameters 
        //    var parameterTypes = GetParameters(member).ToArray();

        //    for (int i = 0; i < parameterTypes.Length; i++)
        //    {
        //        //Load the second method parameter onto the evaluation stack
        //        //This is the parameter array passed to the dynamic method
        //        //iLGenerator.Emit(OpCodes.Ldarg_1);
        //        //Load the index of the current parameter
        //        EmitFastInt(iLGenerator, i);
        //        iLGenerator.Emit(OpCodes.Ldc_I4, i);                
        //        //Load the element at the current index
        //        iLGenerator.Emit(OpCodes.Ldelem_Ref);                
        //        iLGenerator.Emit(OpCodes.Ldelem_Ref);                
        //        EmitFastInt(iLGenerator, i);
        //        iLGenerator.Emit(OpCodes.Stelem_Ref);
        //        //check to see of we need to unbox the value              
        //        if (parameterTypes[i].IsValueType)
        //            iLGenerator.Emit(OpCodes.Unbox_Any, parameterTypes[i]);

        //    }

        //    for (int i = 0; i < parameterTypes.Length; i++)
        //    {
        //        iLGenerator.Emit(OpCodes.Ldarg_1);
        //        EmitFastInt(iLGenerator, i);
        //        iLGenerator.Emit(OpCodes.Stelem_Ref);
        //    }
        //}


        protected virtual LocalBuilder[] LoadParameters(ILGenerator iLGenerator, TMember member)
        {
            //Get the parameter types
            var parameterTypes = GetParameters(member).ToArray();

            //If the parameter is passed by ref, we must get the underlying parameter type
            var localVariableTypes  =  parameterTypes.Select(p => p.IsByRef ? p.GetElementType() : p).ToArray();

            //Create an array of local variables
            LocalBuilder[] localVariables = new LocalBuilder[parameterTypes.Count() + 1];

            //Declare local members to hold the methods arguments
            for (int i = 0; i < parameterTypes.Length; i++)
                localVariables[i] = iLGenerator.DeclareLocal(localVariableTypes[i], true);


            for (int i = 0; i < parameterTypes.Length; i++)
            {
                //Load the second method parameter onto the evaluation stack
                //This is the parameter array passed to the dynamic method
                iLGenerator.Emit(OpCodes.Ldarg_1);
                //Load the index of the current parameter
                EmitFastInt(iLGenerator, i);
                //iLGenerator.Emit(OpCodes.Ldc_I4, i);                
                //Load the element at the current index
                iLGenerator.Emit(OpCodes.Ldelem_Ref);

                //check to see of we need to unbox the value              
                if (parameterTypes[i].IsValueType)
                    iLGenerator.Emit(OpCodes.Unbox_Any, localVariableTypes[i]);
                else
                    iLGenerator.Emit(OpCodes.Castclass,localVariableTypes[i]);

                //Populate the local member variables with the values from the argument array
                iLGenerator.Emit(OpCodes.Stloc, localVariables[i]);
            }

            //Load all the local variables onto the evaluation stack
            for (int i = 0; i < parameterTypes.Length; i++)
            {
                if (parameterTypes[i].IsByRef)
                    //Load the address of the local variable onto the evaluation stack
                    iLGenerator.Emit(OpCodes.Ldloca_S, localVariables[i]);
                else
                    //Loads the value of the local variable onto the evaluation stack
                    iLGenerator.Emit(OpCodes.Ldloc, localVariables[i]);
            }
            return localVariables;
        }

        protected abstract void EmitMethodBody(ILGenerator ilGenerator,TMember target);      

        protected virtual DynamicMethod CreateDynamicMethod(TMember member,bool skipVisibility)
        {
            return new DynamicMethod(string.Empty,
                     typeof(object), new[] { typeof(object), 
                     typeof(object[]) },
                     member.DeclaringType.Module, skipVisibility);
        }



        public abstract IEnumerable<Type> GetParameters(TMember member);
        public abstract Type GetReturnType(TMember member);


        private static void EmitFastInt(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    return;
                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    return;
                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    return;
                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    return;
                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    return;
                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    return;
                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    return;
                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    return;
                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    return;
                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    return;
            }

            if (value > -129 && value < 128)
            {
                il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
            }
            else
            {
                il.Emit(OpCodes.Ldc_I4, value);
            }
        }
    }
}
